﻿using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace DimensionsHelper.SourceGeneration.Enumerator
{

    [Generator(LanguageNames.CSharp)]
    public class EnumConvertSourceGenerator : IIncrementalGenerator
    {

        public Action<EnumGenerationInfoBase> OnSourceEmitting { get; set; }


        private static bool PredicateNode(SyntaxNode node, CancellationToken token)
        {
            return node is ClassDeclarationSyntax classDeclaration
                && !classDeclaration.Modifiers.Any(SyntaxKind.AbstractKeyword)
                && (classDeclaration.Modifiers.Any(SyntaxKind.PublicKeyword) || classDeclaration.Modifiers.Any(SyntaxKind.InternalKeyword))
                && classDeclaration.AttributeLists.Count > 0;
        }


        private static (INamedTypeSymbol Symbol, SemanticModel SemanticModel) Transform(GeneratorAttributeSyntaxContext context, CancellationToken token)
        {
            return (Symbol: context.TargetSymbol as INamedTypeSymbol, context.SemanticModel);
        }


        private void EmitSource(SourceProductionContext context, (INamedTypeSymbol Symbol, SemanticModel SemanticModel) source)
        {
            if (_enumStringAttributeSymbol == null)
            {
                _enumStringAttributeSymbol = source.SemanticModel.Compilation.GetBestTypeByMetadataName(EnumStringAttributeName);
            }

            if (_enumConvertableAttributeSymbol == null)
            {
                _enumConvertableAttributeSymbol = source.SemanticModel.Compilation.GetBestTypeByMetadataName(EnumStringConvertableAttributeName);
            }

            if (_enumStringAttributeSymbol == null || _enumConvertableAttributeSymbol == null)
            {
                return;
            }

            _classSymbol = source.Symbol;
            _diagnosticReporter = context.ReportDiagnostic;

            ProcessClassDeclaration(out var infos);

            foreach (var info in infos)
            {
                OnSourceEmitting?.Invoke(info);

                if (info is EnumStringGenerationInfo enumStringGenerationInfo)
                {
                    // Enum to string
                    context.AddSource($"{enumStringGenerationInfo.ClassName}.{enumStringGenerationInfo.TargetName}.ConvertToString.g.cs",
                        CreateEnumConverterSourceWriter(
                            enumStringGenerationInfo,
                            true,
                            writer => WriteEnumToStringMethod(writer, GetConvertEnumToStringMethodName(enumStringGenerationInfo.TargetName, false), enumStringGenerationInfo))
                        .ToString());
                    // Try enum to string
                    context.AddSource($"{enumStringGenerationInfo.ClassName}.{enumStringGenerationInfo.TargetName}.TryConvertToString.g.cs",
                        CreateEnumConverterSourceWriter(
                            enumStringGenerationInfo,
                            true,
                            writer => WriteTryConvertEnumToStringMethod(writer, GetConvertEnumToStringMethodName(enumStringGenerationInfo.TargetName, true), enumStringGenerationInfo))
                        .ToString());
                    // string to enum
                    context.AddSource($"{info.ClassName}.{enumStringGenerationInfo.TargetName}.ConvertToEnum.g.cs",
                        CreateEnumConverterSourceWriter(
                            enumStringGenerationInfo,
                            enumStringGenerationInfo.ConvertBack,
                            writer => WriteStringToEnumMethod(writer, GetConvertStringToEnumMethodName(enumStringGenerationInfo.TargetName, false), enumStringGenerationInfo))
                        .ToString());
                    // Try enum to string
                    context.AddSource($"{enumStringGenerationInfo.ClassName}.{enumStringGenerationInfo.TargetName}.TryConvertToEnum.g.cs",
                        CreateEnumConverterSourceWriter(
                            enumStringGenerationInfo,
                            enumStringGenerationInfo.ConvertBack,
                            writer => WriteTryConvertStringToEnumMethod(writer, GetConvertStringToEnumMethodName(enumStringGenerationInfo.TargetName, true), enumStringGenerationInfo))
                        .ToString());
                }
                else if (info is EnumAttributePropertySelectorInfo selectorInfo)
                {
                    context.AddSource($"{selectorInfo.ClassName}.{selectorInfo.AttributeType.GetSymbolFullName().ToTypeShortNameWithoutAttribute()}.{selectorInfo.AttributeArgumentName}.{selectorInfo.TargetName}.g.cs",
                        CreateEnumConverterSourceWriter(
                            selectorInfo,
                            true,
                            writer => WriteEnumAttributePropertySelector(writer, selectorInfo))
                        .ToString());
                }
            }
        }


        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
            var symbolsContext = context
                .SyntaxProvider
                .ForAttributeWithMetadataName(
                    EnumConverterAttributeName,
                    PredicateNode,
                    Transform)
                .Where(e => e.Symbol != null)
                .WithTrackingName("EnumSymbol");

            context.RegisterSourceOutput(symbolsContext, EmitSource);
        }


        private const string EnumConverterAttributeName = "DimensionsHelper.Serialization.Enumerator.EnumConverterAttribute";
        private const string EnumStringConvertableAttributeName = "DimensionsHelper.Serialization.Enumerator.EnumStringConvertableAttribute";
        private const string EnumStringAttributeName = "DimensionsHelper.Serialization.Enumerator.EnumStringAttribute";
        private const string EnumAttributePropertySelectorAttributeName = "DimensionsHelper.Serialization.Enumerator.EnumAttributePropertySelectorAttribute";

        private const string SystemMaybeNullWhenAttributeName = "global::System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute";

        private const string MethodConditionArgumentName = "condition";
        private const string MethodOutResultArgumentName = "result";

        private INamedTypeSymbol _enumConvertableAttributeSymbol;
        private INamedTypeSymbol _enumStringAttributeSymbol;
        private INamedTypeSymbol _classSymbol;
        private Action<Diagnostic> _diagnosticReporter;

        private static string GetTryConvertMethodName(EnumAttributePropertySelectorInfo selectorInfo)
        {
            return $"TryConvert{selectorInfo.TargetName.ToTypeShortName()}To{selectorInfo.AttributeType.GetSymbolFullName().ToTypeShortNameWithoutAttribute()}{selectorInfo.AttributeArgumentName}";
        }


        private void ReportDiagnostic(DiagnosticDescriptor descriptor, Location location, params object[] messageArgs)
        {
            _diagnosticReporter?.Invoke(Diagnostic.Create(descriptor, location, messageArgs));
        }


        private static bool IsUsefulEnumAttribute(AttributeData attribute, out bool isEnumStringConvertable, out bool isEnumAttributePropertySelector)
        {
            isEnumStringConvertable = false;
            isEnumAttributePropertySelector = false;

            switch (attribute.AttributeClass.ToDisplayString())
            {
                case EnumStringConvertableAttributeName:
                    isEnumStringConvertable = true;
                    return true;

                case EnumAttributePropertySelectorAttributeName:
                    isEnumAttributePropertySelector = true;
                    return true;

                default:
                    break;
            }

            return false;
        }


        private static string GetConditionParameter(EnumConvertCondition condition, bool appendComma = true)
        {
            if (condition == null)
            {
                return string.Empty;
            }

            var builder = new StringBuilder();

            if (appendComma)
            {
                builder.Append(", ");
            }

            builder.Append($"{condition.TypeFullName} {MethodConditionArgumentName}");

            return builder.ToString();
        }


        private static void FinishSwitchAndMethodBody(SourceWriter writer, string returnValue)
        {
            writer.IndentLevel--;
            writer.AppendLine('}');
            writer.AppendLine();

            if (!string.IsNullOrEmpty(returnValue))
            {
                writer.AppendLine($"return {returnValue};");
            }

            writer.IndentLevel--;
            writer.AppendLine('}');
        }


        private static void WriteEnumMemberToStringCase(SourceWriter writer, EnumConvertValueInfo valueInfo)
        {
            writer.AppendLine();
            writer.AppendLine($"// {valueInfo.EnumMemberFullName}");
            writer.AppendLine($"case {valueInfo.EnumMemberFullName}:");
            writer.IndentLevel++;
            WriteCondition(writer, valueInfo?.GenerationInfo?.Condition, valueInfo.ConditionValue, $"return {valueInfo.GetConvertValue()};");
            writer.IndentLevel--;
        }


        private static string GetEnumForceConvert(EnumConvertValueInfo valueInfo)
        {
            var value = valueInfo.GetConvertValue();

            if (valueInfo.GenerationInfo is EnumAttributePropertySelectorInfo selectorInfo &&
                selectorInfo.IsTargetValueEnum &&
                int.TryParse(value, out _))
            {
                return $"({selectorInfo.PropertyTypeFullName})";
            }

            return string.Empty;
        }


        private static void WriteTryConvertEnumMemerToStringCase(SourceWriter writer, EnumConvertValueInfo valueInfo)
        {
            writer.AppendLine();
            writer.AppendLine($"// {valueInfo.EnumMemberFullName}");
            writer.AppendLine($"case {valueInfo.EnumMemberFullName}:");
            writer.IndentLevel++;
            WriteCondition(writer, valueInfo?.GenerationInfo?.Condition, valueInfo.GetConditionValue(), $@"{MethodOutResultArgumentName} = {valueInfo.GetConvertValue()};
return true;");
            writer.IndentLevel--;
        }


        private static void WriteTryConvertEnumMemberToStringCaseDefault(SourceWriter writer)
        {
            writer.AppendLine($@"
// default
default:
    break;");
        }


        private static void WriteEnumMemberToStringCaseDefault(SourceWriter writer)
        {
            writer.AppendLine($@"
// default
default:
    break;");
        }


        private static void WriteTryConvertStringToEnumMemberCase(SourceWriter writer, EnumConvertValueInfo valueInfo, bool ignoreCase)
        {
            var convertValue = valueInfo.GetConvertValue();
            writer.AppendLine();
            writer.AppendLine($"// {valueInfo.EnumMemberFullName}");
            writer.AppendLine($"case {(ignoreCase ? convertValue.ToLower() : convertValue)}:");
            writer.IndentLevel++;
            WriteCondition(writer, valueInfo?.GenerationInfo?.Condition, valueInfo.GetConditionValue(), $@"{MethodOutResultArgumentName} = {valueInfo.EnumMemberFullName};
return true;");
            writer.IndentLevel--;
        }


        private static void WriteTryConvertStringToEnumMemberCaseDefault(SourceWriter writer)
        {
            writer.AppendLine(@"
// default
default:
    break;");
        }


        private static void WriteTryConvertStringToEnumMethod(SourceWriter writer, string methodName, EnumStringGenerationInfo generationInfo)
        {
            var enumFullName = generationInfo.EnumFullName;
            var ignoreCase = generationInfo.IgnoreCase;

            writer.AppendLine($@"
/// <summary>
/// Try get enumerator of type <see cref=""{enumFullName}""/>, if not exist, <see langword=""false""/> will return.
/// </summary>
public static bool {methodName}(string text{GetConditionParameter(generationInfo.Condition)}, out {enumFullName} {MethodOutResultArgumentName})
{{");
            writer.IndentLevel++;
            writer.AppendLine($"{MethodOutResultArgumentName} = default;");
            writer.AppendLine();
            writer.AppendLine($"switch (text{(ignoreCase ? ".ToLower()" : "")})");
            writer.AppendLine('{');
            writer.IndentLevel++;

            if (generationInfo.EnumValues != null)
            {
                foreach (var info in generationInfo.EnumValues)
                {
                    WriteTryConvertStringToEnumMemberCase(writer, info, ignoreCase);
                }
            }

            WriteTryConvertStringToEnumMemberCaseDefault(writer);
            FinishSwitchAndMethodBody(writer, "false");
        }


        private static void WriteStringToEnumMemberCase(SourceWriter writer, EnumConvertValueInfo valueInfo, bool ignoreCase)
        {
            var text = valueInfo.GetConvertValue();
            writer.AppendLine();
            writer.AppendLine($"// {valueInfo.EnumMemberFullName}");
            writer.AppendLine($"case {(ignoreCase ? text.ToLower() : text)}:");
            writer.IndentLevel++;
            WriteCondition(writer, valueInfo?.GenerationInfo?.Condition, valueInfo.GetConditionValue(), $"return {valueInfo.EnumMemberFullName};");
            writer.IndentLevel--;
        }


        private static void WriteStringToEnumMemberCaseDefault(SourceWriter writer)
        {
            writer.AppendLine(@"
// default
default:
    break;");
        }


        private static void WriteTryConvertEnumToStringMethod(SourceWriter writer, string methodName, EnumStringGenerationInfo generationInfo)
        {
            var enumFullName = generationInfo.EnumFullName;

            writer.AppendLine($@"
/// <summary>
/// Try get text of <see cref=""{enumFullName}""/>, if not exist, return <see langword=""false""/>.
/// </summary>
public static bool {methodName}({enumFullName} obj{GetConditionParameter(generationInfo.Condition)}, [{SystemMaybeNullWhenAttributeName}(false)] out string {MethodOutResultArgumentName})
{{");

            writer.IndentLevel++;
            writer.AppendLine($"{MethodOutResultArgumentName} = null;");
            writer.AppendLine();
            writer.AppendLine("switch (obj)");
            writer.AppendLine('{');
            writer.IndentLevel++;

            if (generationInfo.EnumValues != null)
            {
                foreach (var info in generationInfo.EnumValues)
                {
                    WriteTryConvertEnumMemerToStringCase(writer, info);
                }
            }

            WriteTryConvertEnumMemberToStringCaseDefault(writer);
            FinishSwitchAndMethodBody(writer, "false");
        }


        private static void WriteEnumToStringMethod(SourceWriter writer, string methodName, EnumStringGenerationInfo generationInfo)
        {
            var enumFullName = generationInfo.EnumFullName;

            writer.AppendLine($@"
/// <summary>
/// Get text of <see cref=""{enumFullName}""/>, if not exist, <see cref=""string.Empty""/> will return.
/// </summary>
public static string {methodName}({enumFullName} obj{GetConditionParameter(generationInfo.Condition)})
{{");

            writer.IndentLevel++;
            writer.AppendLine("switch (obj)");
            writer.AppendLine('{');
            writer.IndentLevel++;

            if (generationInfo.EnumValues != null)
            {
                foreach (var info in generationInfo.EnumValues)
                {
                    WriteEnumMemberToStringCase(writer, info);
                }
            }

            WriteEnumMemberToStringCaseDefault(writer);
            FinishSwitchAndMethodBody(writer, "string.Empty");
        }


        private static void WriteStringToEnumMethod(SourceWriter writer, string methodName, EnumStringGenerationInfo generationInfo)
        {
            string enumFullName = generationInfo.EnumFullName;

            writer.AppendLine($@"
/// <summary>
/// Get enumerator of type <see cref=""{enumFullName}""/>, if not exist, <see langword=""default""/> will return.
/// </summary>
public static {enumFullName} {methodName}(string text{GetConditionParameter(generationInfo.Condition)})
{{");

            writer.IndentLevel++;

            writer.AppendLine($@"switch (text{(generationInfo.IgnoreCase ? ".ToLower()" : "")})
{{");
            writer.IndentLevel++;

            if (generationInfo.EnumValues != null)
            {
                foreach (var info in generationInfo.EnumValues)
                {
                    WriteStringToEnumMemberCase(writer, info, generationInfo.IgnoreCase);
                }
            }

            WriteStringToEnumMemberCaseDefault(writer);
            FinishSwitchAndMethodBody(writer, "default");
        }


        private static string GetConvertEnumToStringMethodName(string enumName, bool isTryConvert)
        {
            return $"{(isTryConvert ? "Try" : "")}Convert{enumName}ToString";
        }


        private static string GetConvertStringToEnumMethodName(string enumName, bool isTryConvert)
        {
            return $"{(isTryConvert ? "Try" : "")}ConvertStringTo{enumName}";
        }


        private static SourceWriter CreateEnumConverterSourceWriter(EnumGenerationInfoBase generationInfo, bool condition, Action<SourceWriter> writerAction)
        {
            var writer = new SourceWriter();

            writer.AppendLine($@"
// <auto-generated/>

#nullable enable

namespace {generationInfo.Namespace}
{{");
            writer.IndentLevel++;

            // class
            writer.AppendLine();
            writer.AppendLine($@"{(generationInfo.IsInternal ? "internal" : "public")} {(generationInfo.IsStatic ? "static " : "")}partial class {generationInfo.ClassName}
{{");

            writer.IndentLevel++;

            if (condition)
            {
                writerAction.Invoke(writer);
            }

            writer.AppendLine();
            writer.IndentLevel--;
            writer.AppendLine('}');

            writer.IndentLevel--;
            writer.AppendLine('}');

            return writer;
        }


        private static bool TryGetAttributeValue(AttributeData attribute, EnumAttributePropertySelectorInfo info, out string text)
        {
            text = null;

            if (info is null)
            {
                return false;
            }

            // ConstructorArgIndex 和 PropertyName 两种配置都检查，优先 ConstructorArgIndex

            if (info.AttributeConstructorArgIndex >= 0 && attribute.ConstructorArguments.Length > info.AttributeConstructorArgIndex)
            {
                var arg = attribute.ConstructorArguments[info.AttributeConstructorArgIndex];

                if (arg.Value != null)
                {
                    if (arg.Value is string str)
                    {
                        text = $"\"{str}\"";
                    }
                    else if (arg.Value is char ch)
                    {
                        text = $"'{ch}'";
                    }
                    else
                    {
                        text = arg.Value.ToString();
                    }

                    return true;
                }
            }
            
            if (!string.IsNullOrEmpty(info.AttributeArgumentName))
            {
                foreach (var pair in attribute.NamedArguments)
                {
                    if (pair.Key == info.AttributeArgumentName && pair.Value.Value != null)
                    {
                        if (info.IsTargetValueString)
                        {
                            if (pair.Value.Value is string str)
                            {
                                text = $"\"{str}\"";
                                return true;
                            }

                            return false;
                        }
                        else if (info.IsTargetValueChar)
                        {
                            if (pair.Value.Value is char ch)
                            {
                                text = $"'{ch}'";
                                return true;
                            }

                            return false;
                        }
                        else
                        {
                            text = pair.Value.Value.ToString();
                        }

                        return true;
                    }
                }
            }

            return false;
        }


        private static void WriteCondition(SourceWriter writer, EnumConvertCondition condition, string conditionValue, string innerScript)
        {
            if (string.IsNullOrEmpty(innerScript))
            {
                return;
            }

            if (condition == null || string.IsNullOrEmpty(conditionValue))
            {
                writer.AppendLine(innerScript);
            }
            else
            {
                string conditionExpression;

                if (condition.IsFlag)
                {
                    conditionExpression = $"((int){MethodConditionArgumentName} & {conditionValue}) != 0";
                }
                else
                {
                    conditionExpression = $"{MethodConditionArgumentName} == {conditionValue}";
                }

                writer.AppendLine($"if ({conditionExpression})");
                writer.AppendLine('{');

                writer.IndentLevel++;
                writer.AppendLine(innerScript);
                writer.IndentLevel--;
                writer.AppendLine('}');
                writer.AppendLine("break;");
            }
        }


        private static void WriteEnumAttributePropertySelector(SourceWriter writer, EnumAttributePropertySelectorInfo selectorInfo)
        {

            writer.AppendLine($@"
/// <summary>
/// Try convert <see cref=""{selectorInfo.EnumFullName}""/> to <see cref=""{selectorInfo.PropertyTypeFullName}""/>, return <see langword=""true""/> if succeed.
/// </summary>
public static bool {GetTryConvertMethodName(selectorInfo)}({selectorInfo.EnumFullName} arg{GetConditionParameter(selectorInfo.Condition)}, {(selectorInfo.IsNullable || !selectorInfo.IsValueType ? $"[{SystemMaybeNullWhenAttributeName}(false)] " : "")}out {selectorInfo.PropertyTypeFullName}{(selectorInfo.IsNullable ? "?" : "")} {MethodOutResultArgumentName})
{{");

            writer.IndentLevel++;

            if (selectorInfo.IsNullable || !selectorInfo.IsValueType)
            {
                writer.AppendLine($"{MethodOutResultArgumentName} = null;");
            }
            else
            {
                writer.AppendLine($"{MethodOutResultArgumentName} = default;");
            }

            writer.AppendLine();
            writer.AppendLine($@"switch (arg)");
            writer.AppendLine('{');
            writer.IndentLevel++;

            if (selectorInfo.EnumValues != null)
            {
                foreach (var info in selectorInfo.EnumValues)
                {
                    WriteEnumMemberSelectorCase(writer, info);
                }
            }

            // case default
            writer.AppendLine(@"
default:
    break;");

            writer.AppendLine();
            FinishSwitchAndMethodBody(writer, "false");
        }



        private static void WriteEnumMemberSelectorCase(SourceWriter writer, EnumConvertValueInfo valueInfo)
        {
            if (!(valueInfo.GenerationInfo is EnumAttributePropertySelectorInfo selectorInfo))
            {
                return;
            }

            writer.AppendLine();
            writer.AppendLine($"// {valueInfo.EnumMemberFullName} -> {selectorInfo.PropertyTypeFullName}");
            writer.AppendLine($"case {valueInfo.EnumMemberFullName}:");
            writer.IndentLevel++;
            WriteCondition(writer, valueInfo?.GenerationInfo?.Condition, valueInfo.GetConditionValue(), $@"{MethodOutResultArgumentName} = {GetEnumForceConvert(valueInfo)}{valueInfo.GetConvertValue()};
return true;");
            writer.IndentLevel--;
        }


        private readonly struct EnumMemberProcessor
        {

            public EnumMemberProcessor(EnumGenerationInfoBase info, string attributeName, Action<EnumGenerationInfoBase, ISymbol, AttributeData> action)
            {
                Info = info;
                AttributeName = attributeName;
                AttributeSymbol = null;
                Action = action;
            }


            public EnumMemberProcessor(EnumGenerationInfoBase info, ITypeSymbol attributeSymbol, Action<EnumGenerationInfoBase, ISymbol, AttributeData> action)
            {
                Info = info;
                AttributeName = null;
                AttributeSymbol = attributeSymbol;
                Action = action;
            }


            private EnumGenerationInfoBase Info { get; }


            private string AttributeName { get; }


            private ITypeSymbol AttributeSymbol { get; }


            private Action<EnumGenerationInfoBase, ISymbol, AttributeData> Action { get; }


            public void Run(ISymbol enumMemberSymbol, AttributeData attribute)
            {
                if (attribute.AttributeClass.ToDisplayString() == AttributeName || (
                    AttributeSymbol != null && AttributeSymbol.IsAssignableFrom(attribute.AttributeClass)))
                {
                    Action?.Invoke(Info, enumMemberSymbol, attribute);
                }
            }

        }


        private static string GetConditionArgText(EnumConvertCondition condition, AttributeData attribute)
        {
            if (condition == null || attribute == null)
            {
                return null;
            }

            string value = attribute.GetConstructorValueAt(condition.ConstructorArgIndex)?.ToString();

            if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(condition.ArgumentName))
            {
                value = attribute.GetNamedArgumentValue(condition.ArgumentName)?.ToString();
            }

            return value;
        }


        private static void AddExistEnumConvertValueInfo(EnumGenerationInfoBase info, string enumMemberFullName, string targetValue, string conditionValue)
        {
            if (info.EnumValues == null)
            {
                info.EnumValues = new List<EnumConvertValueInfo>();
            }

            EnumConvertValueInfo existValueInfo = info.EnumValues.Find(e => e.EnumMemberFullName == enumMemberFullName);

            if (existValueInfo != null)
            {
                existValueInfo.Value = targetValue;
            }
            else
            {
                info.EnumValues.Add(new EnumConvertValueInfo
                {
                    GenerationInfo = info,
                    EnumMemberFullName = enumMemberFullName,
                    ConditionValue = conditionValue,
                    Value = targetValue
                });
            }
        }


        private void ProcessEnumMemberStringAttribute(EnumGenerationInfoBase info, ISymbol enumMemberSymbol, AttributeData attribute)
        {

            // 如果指定了特性类型，则必须类型相同才继续
            if (info.AttributeType != null)
            {
                // 检查是否允许派生类
                if (!info.AttributeDerivedType && !SymbolEqualityComparer.Default.Equals(info.AttributeType, attribute.AttributeClass))
                {
                    return;
                }

                if (info.AttributeDerivedType && !info.AttributeType.IsAssignableFrom(attribute.AttributeClass))
                {
                    return;
                }
            }

            bool isEnumStringAttribute = SymbolEqualityComparer.Default.Equals(
                _enumStringAttributeSymbol, attribute.AttributeClass);

            // 未指定特性类型时，检查是否是 EnumStringAttribute
            if (info.AttributeType == null && !isEnumStringAttribute)
            {
                if (!info.AttributeDerivedType ||
                    !_enumStringAttributeSymbol.IsAssignableFrom(attribute.AttributeClass))
                {
                    return;
                }
            }

            string conditionArgValue = GetConditionArgText(info.Condition, attribute);
            string enumString = null;

            // 如果是EnumStringAttribute，使用第一个参数
            if (isEnumStringAttribute)
            {
                if (attribute.ConstructorArguments.Length > 0 &&
                    attribute.ConstructorArguments[0].Value is string attrEnumStr)
                {
                    enumString = attrEnumStr;
                }
            }
            // 先检查构造参数索引
            else if (info.AttributeConstructorArgIndex >= 0)
            {
                enumString = attribute.GetConstructorValueAt(info.AttributeConstructorArgIndex)?.ToString();
            }
            // 查找命名参数
            else if (!string.IsNullOrEmpty(info.AttributeArgumentName))
            {
                enumString = attribute.GetNamedArgumentValue(info.AttributeArgumentName)?.ToString();
            }

            // 忽略空字符串
            if (string.IsNullOrEmpty(enumString))
            {
                return;
            }

            string enumMemberFullName = enumMemberSymbol.GetSymbolFullName();
            AddExistEnumConvertValueInfo(info, enumMemberFullName, enumString, conditionArgValue);
        }


        private void ProcessEnumMemberSelectorAttribute(EnumGenerationInfoBase info, ISymbol enumMemberSymbol, AttributeData attribute)
        {
            if (attribute == null || enumMemberSymbol == null)
            {
                return;
            }

            if (!TryGetAttributeValue(attribute, info as EnumAttributePropertySelectorInfo, out var selectorValue))
            {
                return;
            }

            string conditionValue = GetConditionArgText(info.Condition, attribute);
            string enumMemberName = enumMemberSymbol.GetSymbolFullName();
            AddExistEnumConvertValueInfo(info, enumMemberName, selectorValue, conditionValue);
        }


        private static void ProcessEnumMemberAttribute(ITypeSymbol enumSymbol, params EnumMemberProcessor[] processors)
        {
            foreach (var member in enumSymbol.GetMembers())
            {
                foreach (var attributeData in member.GetAttributes())
                {
                    foreach (var processor in processors)
                    {
                        processor.Run(member, attributeData);
                    }
                }
            }
        }


        private EnumAttributePropertySelectorInfo ProcessEnumSymbolPropertySelector(AttributeData attribute)
        {
            ITypeSymbol enumSymbol = null;
            ITypeSymbol attrSymbol = null;
            bool isString = false;
            bool isChar = false;
            bool isEnum = false;
            bool isValueType = false;
            bool isNullable = false;

            string argumentName = string.Empty;
            int ctorIndex = -1;

            EnumConvertCondition condition = null;

            if (attribute.ConstructorArguments.Length == 2)
            {
                var _1st = attribute.ConstructorArguments[0];
                var _2nd = attribute.ConstructorArguments[1];

                if (_1st.Value is ITypeSymbol enumTypeSymbol)
                {
                    enumSymbol = enumTypeSymbol;
                }

                if (_2nd.Value is ITypeSymbol attrTypeSymbol)
                {
                    attrSymbol = attrTypeSymbol;
                }
            }

            ITypeSymbol conditionType = null;
            int conditonConstructorArgIndex = -1;
            string conditionArgumentName = null;

            foreach (var pair in attribute.NamedArguments)
            {
                switch (pair.Key)
                {
                    case "ArgumentName" when pair.Value.Value is string pName:
                        argumentName = pName;
                        break;

                    case "ConstructorArgIndex" when pair.Value.Value is int index:
                        ctorIndex = index;
                        break;

                    case "ConditionType" when pair.Value.Value is ITypeSymbol conditionTypeSymbol:
                        conditionType = conditionTypeSymbol;
                        break;

                    case "ConditionConstructorArgIndex" when pair.Value.Value is int conditionCtorArgIndex:
                        conditonConstructorArgIndex = conditionCtorArgIndex;
                        break;

                    case "ConditionArgumentName" when pair.Value.Value is string conditionArgName:
                        conditionArgumentName = conditionArgName;
                        break;

                    default:
                        break;
                }

                if (conditionType != null && (conditonConstructorArgIndex >= 0 || !string.IsNullOrEmpty(conditionArgumentName)))
                {
                    condition = new EnumConvertCondition
                    {
                        IsFlag = conditionType.IsFlags(),
                        ArgumentName = conditionArgumentName,
                        ConstructorArgIndex = conditonConstructorArgIndex,
                        TypeFullName = conditionType.GetSymbolFullName(),
                    };
                }
            }

            if (enumSymbol == null || attrSymbol == null || (
                string.IsNullOrEmpty(argumentName) && ctorIndex < 0))
            {
                return null;
            }

            if (enumSymbol.TypeKind != TypeKind.Enum)
            {
                ReportDiagnostic(EnumDiagnostics.TypeIsNotAnEnumerator, attribute.GetLocation());
                return null;
            }

            string propertyTypeFullName = string.Empty;

            if (!string.IsNullOrEmpty(argumentName))
            {
                var properties = attrSymbol.FindMembers(argumentName);

                if (properties.IsEmpty)
                {
                    ReportDiagnostic(EnumDiagnostics.PropertyIsNotExist, attribute.GetLocation());
                    return null;
                }

                if (!(properties[0] is IPropertySymbol _1st))
                {
                    ReportDiagnostic(EnumDiagnostics.MemberIsNotProperty, attribute.GetLocation());
                    return null;
                }

                switch (_1st.ToDisplayString())
                {
                    case "char":
                        isChar = true;
                        break;

                    case "string":
                        isString = true;
                        break;

                    default:
                        isEnum = _1st.Type.IsEnum();
                        break;
                }
                
                propertyTypeFullName = _1st.Type.GetSymbolFullName();
                isValueType = _1st.Type.IsValueType;
                isNullable = _1st.Type.NullableAnnotation.HasFlag(NullableAnnotation.Annotated);
            }
            
            return new EnumAttributePropertySelectorInfo
            {
                ClassName = _classSymbol.Name,
                Namespace = _classSymbol.ContainingNamespace.ToDisplayString(),
                IsStatic = _classSymbol.IsStatic,
                IsInternal = _classSymbol.DeclaredAccessibility == Accessibility.Internal,
                TargetName = enumSymbol.Name,
                EnumFullName = enumSymbol.GetSymbolFullName(),
                IsTargetValueString = isString,
                IsTargetValueChar = isChar,
                IsTargetValueEnum = isEnum,
                IsValueType = isValueType,
                IsNullable = isNullable,
                AttributeType = attrSymbol,
                AttributeArgumentName = argumentName,
                PropertyTypeFullName = propertyTypeFullName,
                AttributeConstructorArgIndex = ctorIndex,
                Condition = condition,
            };
        }


        private EnumStringGenerationInfo ProcessEnumStringConvertable(ITypeSymbol enumSymbol, AttributeData attribute)
        {
            bool ignoreCase = false;
            bool convertBack = true;
            EnumConvertCondition condition = null;
            ITypeSymbol conditionType = null;
            int conditionConstructorArgIndex = -1;
            string conditionArgumentName = null;
            ITypeSymbol attributeTypeSymbol = null;
            int constructorArgIndex = -1;
            string constructorArgArgumentName = null;
            bool attributeDerivedType = false;
            string targetName = null;

            foreach (var arg in attribute.NamedArguments)
            {
                switch (arg.Key)
                {
                    case "IgnoreCase" when arg.Value.Value is bool blnIgnoreCase:
                        ignoreCase = blnIgnoreCase;
                        break;

                    case "ConvertBack" when arg.Value.Value is bool blnConvertBack:
                        convertBack = blnConvertBack;
                        break;

                    case "ConditionType" when arg.Value.Value is ITypeSymbol conditionTargetType:
                        conditionType = conditionTargetType;
                        break;

                    case "ConditionConstructorArgIndex" when arg.Value.Value is int conditionCtorArgIndex:
                        conditionConstructorArgIndex = conditionCtorArgIndex;
                        break;

                    case "ConditionArgumentName" when arg.Value.Value is string conditionArgName:
                        conditionArgumentName = conditionArgName;
                        break;

                    case "AttributeType" when arg.Value.Value is ITypeSymbol attrTypeSymbol:
                        attributeTypeSymbol = attrTypeSymbol;
                        break;

                    case "AttributeConstructorArgIndex" when arg.Value.Value is int attrCtorArgIndex:
                        constructorArgIndex = attrCtorArgIndex;
                        break;

                    case "AttributeArgumentName" when arg.Value.Value is string attrCtorArgName:
                        constructorArgArgumentName = attrCtorArgName;
                        break;

                    case "AttributeDerivedType" when arg.Value.Value is bool attrDerivedType:
                        attributeDerivedType = attrDerivedType;
                        break;

                    case "TargetName" when arg.Value.Value is string targetNameStr:
                        targetName = targetNameStr;
                        break;

                    default:
                        break;
                }
            }

            if (conditionType != null && (conditionConstructorArgIndex >= 0 || !string.IsNullOrEmpty(conditionArgumentName)))
            {
                condition = new EnumConvertCondition
                {
                    IsFlag = conditionType.IsFlags(),
                    TypeFullName = conditionType.GetSymbolFullName(),
                    ArgumentName = conditionArgumentName,
                    ConstructorArgIndex = conditionConstructorArgIndex,
                };
            }

            return new EnumStringGenerationInfo
            {
                ClassName = _classSymbol.Name,
                Namespace = _classSymbol.ContainingNamespace.ToDisplayString(),
                IsStatic = _classSymbol.IsStatic,
                IsInternal = _classSymbol.DeclaredAccessibility == Accessibility.Internal,
                TargetName = targetName ?? enumSymbol.Name,
                EnumFullName = enumSymbol.GetSymbolFullName(),
                AttributeType = attributeTypeSymbol,
                AttributeConstructorArgIndex = constructorArgIndex,
                AttributeArgumentName = constructorArgArgumentName,
                AttributeDerivedType = attributeDerivedType,
                IsTargetValueString = true,
                IsTargetValueChar = false,
                IsTargetValueEnum = false,
                Condition = condition,
                IgnoreCase = ignoreCase,
                ConvertBack = convertBack,
            };
        }


        private void ProcessClassDeclaration(out List<EnumGenerationInfoBase> infos)
        {
            infos = new List<EnumGenerationInfoBase>();

            if (_classSymbol == null)
            {
                return;
            }

            var processors = new Dictionary<string, (ITypeSymbol EnumSymbol, List<EnumMemberProcessor> Processors)>();

            void addProcessor(ITypeSymbol enumSymbol, EnumMemberProcessor processor)
            {
                var enumName = enumSymbol.ToDisplayString();
                if (!processors.TryGetValue(enumName, out var tuple))
                {
                    tuple = (enumSymbol, new List<EnumMemberProcessor>());
                    processors.Add(enumName, tuple);
                }

                tuple.Processors.Add(processor);
            }

            foreach (var attr in _classSymbol.GetAttributes())
            {
                if (!IsUsefulEnumAttribute(attr, out bool isEnumConvertable, out bool isAttributePropertySelector))
                {
                    continue;
                }

                if (attr.ConstructorArguments.Length < 1 || !(attr.ConstructorArguments[0].Value is ITypeSymbol typeSymbol))
                {
                    continue;
                }

                if (isEnumConvertable)
                {
                    var stringConvertableInfo = ProcessEnumStringConvertable(typeSymbol, attr);
                    if (stringConvertableInfo != null)
                    {
                        infos.Add(stringConvertableInfo);
                        addProcessor(typeSymbol, new EnumMemberProcessor(stringConvertableInfo, _enumStringAttributeSymbol, ProcessEnumMemberStringAttribute));
                    }
                }
                else if (isAttributePropertySelector)
                {
                    var selectorInfo = ProcessEnumSymbolPropertySelector(attr);
                    if (selectorInfo != null)
                    {
                        infos.Add(selectorInfo);
                        addProcessor(typeSymbol, new EnumMemberProcessor(selectorInfo, selectorInfo.AttributeType, ProcessEnumMemberSelectorAttribute));
                    }
                }
            }

            foreach (var pair in processors)
            {
                ProcessEnumMemberAttribute(pair.Value.EnumSymbol, pair.Value.Processors.ToArray());
            }

        }
    }
}
